//
//  ORMTCKeyValueStore.m
//  ORM
//
//  Created by PH on 21.12.09.
//  Copyright 2009-2010 Codesign. All rights reserved.
//

#import "ORMTCKeyValueStore.h"
#import "EXNSAdditions.h"

@implementation ORMTCKeyValueStore

- (id)initWithPath:(NSString*)path autosync:(BOOL)_autosync {
	if ((self = [super init])) {
		tdb = tctdbnew();
		if(tctdbopen(tdb, [path UTF8String], _autosync ? (TDBOWRITER | TDBOCREAT | TDBOTSYNC) :
					 (TDBOWRITER | TDBOCREAT))) {
			isReady = YES;
			//bool indexSet0 = tctdbsetindex(tdb, "", TDBITLEXICAL | TDBITKEEP);
			tctdbsetindex(tdb, "type", TDBITLEXICAL | TDBITKEEP);
			tctdbsetindex(tdb, "class", TDBITLEXICAL | TDBITKEEP);
			//bool indexSet3 = tctdbsetindex(tdb, "name", TDBITLEXICAL | TDBITKEEP);
			//bool indexSet4 = tctdbsetindex(tdb, "objectID", TDBITLEXICAL | TDBITKEEP);
			//bool indexSet5 = tctdbsetindex(tdb, "value", TDBITLEXICAL | TDBITKEEP);
			//if (indexSet0 || indexSet1 || indexSet2 || indexSet3 || indexSet4 || indexSet5)
			//	NSLog(@"Index(es) created (KVStore): %d %d %d %d %d %d", indexSet0, indexSet1, indexSet2, indexSet3, indexSet4, indexSet5);
			lock = [[NSLock alloc] init];
		} else {
			int ecode = tctdbecode(tdb);
			NSLog(@"KVStore open error: %s", tctdberrmsg(ecode));
		}
	}
	return self;
}

- (BOOL)createIndexOnField:(NSString*)fieldName rebuild:(BOOL)rebuild {
	return tctdbsetindex(tdb, [fieldName UTF8String], TDBITLEXICAL | (rebuild ? 0 : TDBITKEEP)) == true;
}

- (BOOL)isReady {
	return isReady;
}

- (void(^)(void*))stringEqualConditionWithColumnName:(NSString*)columnName value:(NSString*)value {
	return [[^void(void* qry) {
		tctdbqryaddcond((TDBQRY*) qry, [columnName UTF8String], TDBQCSTREQ, [value UTF8String]);
	} copy] autorelease];
}

- (void(^)(void*))stringIncludedConditionWithColumnName:(NSString*)columnName value:(NSString*)value {
	return [[^void(void* qry) {
		tctdbqryaddcond((TDBQRY*) qry, [columnName UTF8String], TDBQCSTRINC, [value UTF8String]);
	} copy] autorelease];
}

- (void(^)(void*))numberLessThanConditionWithColumnName:(NSString*)columnName value:(double)value {
	return [[^void(void* qry) {
		tctdbqryaddcond((TDBQRY*) qry, [columnName UTF8String], TDBQCNUMLT, [[[NSNumber numberWithDouble: value] stringValue] UTF8String]);
	} copy] autorelease];
}

- (void(^)(void*))numberGreaterThanConditionWithColumnName:(NSString*)columnName value:(double)value {
	return [[^void(void* qry) {
		tctdbqryaddcond((TDBQRY*) qry, [columnName UTF8String], TDBQCNUMGT, [[[NSNumber numberWithDouble: value] stringValue] UTF8String]);
	} copy] autorelease];
}

- (void(^)(void*))numberEqualConditionWithColumnName:(NSString*)columnName value:(double)value {
	return [[^void(void* qry) {
		tctdbqryaddcond((TDBQRY*) qry, [columnName UTF8String], TDBQCNUMEQ, [[[NSNumber numberWithDouble: value] stringValue] UTF8String]);
	} copy] autorelease];
}

- (BOOL)putMap:(NSDictionary*)dict forKey:(NSString*)key {
	TCMAP* map = tcmapnew();
	for (NSString* _key in [dict allKeys]) {
		id value = [dict objectForKey: _key];
		if ([value isKindOfClass: [NSString class]]) {
			tcmapput2(map, [_key UTF8String], [value UTF8String]);
		} else {
			NSData* _keyData = [_key dataUsingEncoding: NSUTF8StringEncoding];
			NSData* data = [value isKindOfClass: [NSData class]] ? value : [value dataUsingEncoding: NSUTF8StringEncoding];
			tcmapput(map, [_keyData bytes], [_keyData length], [data bytes], [data length]);
		}
	}
	NSData* keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
	BOOL success = tctdbput(tdb, [keyData bytes], [keyData length], map);
	if (success == NO) {
		int ecode = tctdbecode(tdb);
		NSLog(@"KVStore put error: %s", tctdberrmsg(ecode));
	}
	tcmapdel(map);
	return success;
}

- (NSDictionary*)getMapForKey:(NSString*)key {
	NSData* _keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
	TCMAP* map = tctdbget(tdb, [_keyData bytes], [_keyData length]);
	if (map) {
		NSMutableDictionary* dict = [NSMutableDictionary dictionary];
		[dict setObject: key forKey: @"__key"];
		tcmapiterinit(map);
		const char* name;
		while((name = tcmapiternext2(map)) != NULL) {
			int length;
			const void* bytes = tcmapget(map, name, strlen(name), &length);
			NSData* data = [[NSData alloc] initWithBytes: bytes length: length];
			NSAssert(name != NULL, @"C-string is NULL");
			[dict setObject: data forKey: [NSString stringWithUTF8String: name]];
			[data release];
		}
		tcmapdel(map);
		return dict;
	} else return nil;
}

- (NSArray*)getMapsWithCriteria:(NSDictionary*)criteria {
	return [self getMapsWithCriteria: criteria condBlock: nil];
}

- (NSArray*)getMapsWithCriteria:(NSDictionary*)criteria condBlock:(void(^)(void*))condBlock {
	TDBQRY* qry = tctdbqrynew(tdb);
	NSMutableSet* objectIDs = [[NSMutableSet alloc] init];
	NSMutableArray* records = [NSMutableArray array];
	for (NSString* _key in [criteria allKeys]) {
		NSString* value = [criteria objectForKey: _key];
		tctdbqryaddcond(qry, [_key UTF8String], TDBQCSTREQ, [value UTF8String]);
	}
	if (condBlock != nil) condBlock(qry);
	//tctdbqrysetorder(qry, "name", TDBQOSTRASC);
	//tctdbqrysetlimit(qry, 10, 0);
	TCLIST* res = tctdbqrysearch(qry);
	for(int i = 0; i < tclistnum(res); i++){
		int rsiz;
		const char* rbuf = tclistval(res, i, &rsiz);
		NSAssert(rbuf != NULL, @"C-string is NULL");
		//NSString* objectID = [NSString stringWithUTF8String: rbuf];
		NSString* objectID = [[NSString alloc] initWithBytes: rbuf length: rsiz encoding: NSUTF8StringEncoding];
		[objectIDs addObject: objectID];
		[objectID release]; //
	}
	tclistdel(res);
	for (NSString* objectID in objectIDs) {
		TCMAP* map = tctdbget(tdb, [objectID UTF8String], [objectID length]);
		if (map) {
			NSString* key = objectID; //[[[NSString alloc] initWithBytes: rbuf length: rsiz encoding: NSUTF8StringEncoding] autorelease];
			//NSLog(@"key: %@", key);
			NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
			[dict setObject: key forKey: @"__key"];
			tcmapiterinit(map);
			const char* name;
			while((name = tcmapiternext2(map)) != NULL) {
				int length;
				const void* bytes = tcmapget(map, name, strlen(name), &length);
				NSData* data = [[NSData alloc] initWithBytes: bytes length: length];
				NSAssert(name != NULL, @"C-string is NULL");
				[dict setObject: data forKey: [NSString stringWithUTF8String: name]];
				[data release];
			}
			tcmapdel(map);
			[records addObject: dict];
			[dict release];
		}
	}
	tctdbqrydel(qry);
	[objectIDs release];
	return records;
}

- (BOOL)removeRecordsMatchingCriteria:(NSDictionary*)criteria {
	TDBQRY* qry = tctdbqrynew(tdb);
	for (NSString* _key in [criteria allKeys]) {
		NSString* value = [criteria objectForKey: _key];
		tctdbqryaddcond(qry, [_key UTF8String], TDBQCSTREQ, [value UTF8String]);
	}
	BOOL success = tctdbqrysearchout(qry) == true;
	tctdbqrydel(qry);
	return success;
}

- (BOOL)removeMapForKey:(NSString*)key {
	NSData* keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
	return tctdbout(tdb, [keyData bytes], [keyData length]) == true;
}

- (BOOL)beginTransaction {
	[lock lock];
	NSAssert(transactionStart == nil, @"Transaction start date isn't nil");
	transactionStart = [[NSDate alloc] init];
	BOOL success = tctdbtranbegin(tdb) == true;
	NSAssert(success == YES, @"Couldn't begin KVStore transaction"); //: %@", [self lastErrorString]);
	return success;
}

- (BOOL)commitTransaction {
	//NSLog(@"commiting");
	BOOL success = tctdbtrancommit(tdb) == true;
	NSAssert(success == YES, @"Couldn't commit KVStore transaction"); //: %@", [self lastErrorString]);
	//NSLog(@"Transaction duration: %lf", -[transactionStart timeIntervalSinceNow]);
	[transactionStart release];
	transactionStart = nil;
	[lock unlock];
	return success;
}

- (BOOL)rollbackTransaction {
	BOOL success = tctdbtranabort(tdb) == true;
	NSAssert(success == YES, @"Couldn't rollback KVStore transaction"); //: %@", [self lastErrorString]);
	//NSLog(@"Transaction duration: %lf", -[transactionStart timeIntervalSinceNow]);
	[transactionStart release];
	transactionStart = nil;
	[lock unlock];
	return success;
}

- (NSString*)lastErrorString {
	int errorCode = tctdbecode(tdb);
	const char* errorMessage = tctdberrmsg(errorCode);
	NSAssert(errorMessage != NULL, @"C-string is NULL");
	return [NSString stringWithUTF8String: errorMessage];
}

- (NSString*)uniqueKey {
	return [[[NSString stringWithFormat: @"%@:%ld", [NSDate date], random()] base64String]
			stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

- (void)close {
	if (isClosed == NO) {
		isClosed = YES;
		if(isReady && !tctdbclose(tdb)){
			int ecode = tctdbecode(tdb);
			NSLog(@"KVStore close error: %s", tctdberrmsg(ecode));
		}
		tctdbdel(tdb);
	}
}

- (void)dealloc {
	[self close];
	while ([lock tryLock] == NO) [NSThread sleepForTimeInterval: 0.1];
	[lock unlock];
	[lock release];
	[transactionStart release];
	[super dealloc];
}

@end
